﻿using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Threading;

using RAD.LightOne;
using RAD.LightOne;

namespace RAD.LightOne.Cache
{
    #region Documentation
    /// <summary>
    /// Classe que implementa um pool e classes de assemblies diversos que dividem a mesma pasta e herdam de MyObject, funciona
    /// como um cache de tipos e tem utilidades diversar dentro do Light. Várias instâncias, cache único.
    /// </summary>
    #endregion Documentation
    internal sealed class TypesPool : ICollection<Type>
    {
        #region Fields

        private static Dictionary<string,Type> classes;

        #endregion

        #region Properties
        #region Documentation
        /// <summary>
        /// Retorna um tipo por sua chave, que é o FullName do tipo.
        /// </summary>
        /// <param name="key">Type.FullName.</param>
        #endregion 
        [System.Runtime.CompilerServices.IndexerName("GetItem")]
        public Type this[string key]
        {
            get
            {
                return classes[key];
            }
        }

        #endregion Properties

        #region Constructors

        static TypesPool()
        {
            classes = new Dictionary<string, Type>();

            List<string> directoryes = new List<string>();

            try
            {
                directoryes.AddRange(GetFolderTree(AppDomain.CurrentDomain.DynamicDirectory));
            }
            catch { }
            try
            {
                directoryes.AddRange(GetFolderTree(AppDomain.CurrentDomain.BaseDirectory));
            }
            catch { }
            try
            {
                directoryes.AddRange(GetFolderTree(Path.GetDirectoryName(typeof(TypesPool).Assembly.Location)));
            }
            catch { }

            foreach (string directory in directoryes)
            {
                if (!string.IsNullOrEmpty(directory))
                {
                    foreach (string fileName in Directory.GetFiles(directory, "*.exe"))
                    {
                        try
                        {
                            Assembly assembly = Assembly.LoadFrom(fileName);
                            FillList(classes, assembly);
                        }
                        catch { }
                    }

                    foreach (string fileName in Directory.GetFiles(directory, "*.dll"))
                    {
                        try
                        {
                            if (File.Exists(fileName))
                            {
                                try
                                {
                                    Assembly assembly = Assembly.LoadFrom(fileName);
                                    FillList(classes, assembly);
                                }
                                catch { }
                            }
                        }
                        catch { }
                    }
                }
            }

        }

        #region Documentation
        /// <summary>
        /// Retorna uma varredura de todas as pastas a partir de e inclusive as pastas passadas no parâmetro.
        /// </summary>
        #endregion Documentation
        private static string[] GetFolderTree(params string[] paths)
        {
            List<string> folders = new List<string>();

            if (paths.Length > 0)
            {
                foreach (string path in paths)
                {
                    folders.Add(path);

                    try
                    {
                        if (!string.IsNullOrEmpty(path))
                        {
                            string[] subfolders = Directory.GetDirectories(path);
                            if (subfolders.Length > 0)
                            {
                                folders.AddRange(GetFolderTree(subfolders));

                            }
                        }
                    }
                    catch { }
                }
            }

            return folders.ToArray();
        }

        private static void FillList(IDictionary<string,Type> list, Assembly assembly)
        {
            foreach (Type type in assembly.GetTypes())
            {
                if (type.GetInterface(typeof(IMyObject).FullName) != null && !classes.ContainsKey(type.FullName))
                {
                    classes.Add(type.FullName, type);
                }
            }
        }

        public TypesPool() { }

        #endregion

        #region Methods

        #region Documentation
        /// <summary>
        /// Método que executa um carregamento através do construtor estático da classe.
        /// Chamá-lo ou não em nada atrapalha a utilização das funcionalidades da classe, porém sua chamada prematura
        /// faz com que os carregamentos se dêem de forma otimizada, melhorando o desempenho.
        /// </summary>
        #endregion
        public static void Load(object stateInfo)
        { 
            ((AutoResetEvent)stateInfo).Set(); 
        }

        public static void Load()
        {
        }

        #endregion

        #region ICollection<Type> Members

        public void Add(Type item)
        {
            classes.Add(item.FullName, item);
        }

        [System.Obsolete("Don't use this method the collection is read only.",true)]
        public void Clear()
        {
            classes.Clear();
        }

        public bool Contains(Type item)
        {
            return classes.ContainsValue(item);
        }

        public void CopyTo(Type[] array, int arrayIndex)
        {
            classes.Values.CopyTo(array, arrayIndex);
        }

        public void CopyTo(Type[] array)
        {
            classes.Values.CopyTo(array, 0);
        }

        public int Count
        {
            get { return classes.Count; }
        }

        public bool IsReadOnly
        {
            get { return true; }
        }

        public bool Remove(Type item)
        {
            return classes.Remove(item.FullName);
        }

        #endregion

        #region IEnumerable<Type> Members

        public IEnumerator<Type> GetEnumerator()
        {
            return classes.Values.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)classes).GetEnumerator();
        }

        #endregion
    }
}
